/*
 *  KBServer.cpp
 *  GRE
 *
 *  Created by Kutlak Roman on 24/01/2012.
 *  Copyright 2012 Kutlak Roman. All rights reserved.
 *
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <iostream>
#include <sstream>
#include <vector>

#include "KBServer.h"
#include "Ontology.h"
#include "Settings.h"

namespace kutlak {
namespace roman {
namespace gre {
    
    // class implemented
    /////////////////////////////// PUBLIC   ///////////////////////////////////
    
#pragma mark -
#pragma mark lifecycle
    KBServer::KBServer(const string& path, size_t port) :
    fExit(0), mSocket(0), mAddressLen(0)
    {
        // create a new socket
        mSocket = socket(AF_INET, SOCK_STREAM, 0);
        
        mAddress.sin_family = AF_INET;
        mAddress.sin_addr.s_addr = htonl(INADDR_ANY);//inet_addr("127.0.0.1");
        mAddress.sin_port = htons(port);
        mAddressLen = sizeof(mAddress);
        
        // bind to the socket
        bind(mSocket, (struct sockaddr *)&mAddress, mAddressLen);
        
        // listen for clients with a queue of up to 5 connections
        listen(mSocket, 5);
        
        string ontologyPath = Settings::Instance().GetSetting("DefaultOntology");
        Ontology::SetDefault(pOntology(new Ontology(ontologyPath)));
        string kbPath;
        if ("" == path)
            kbPath = Settings::Instance().GetSetting("DefaultKnowledgeBasePath");
        else
            kbPath = path;
        mpKnowledgeBase = pKnowledgeBase(new KnowledgeBase(kbPath));
#ifdef DEBUG
        std::cout << "Number of entities: " << mpKnowledgeBase->Size() << '\n';
#endif
    }
    
    KBServer::~KBServer()
    {
#ifdef DEBUG
        std::cout << "Exiting server.\n";
#endif        
        close(mSocket);
    }
    
#pragma mark -
#pragma mark operations
    //=============================  OPERATIONS  ===================================
    
    // Wait for a client and send data upon connection.
    void KBServer::Run()
    {
        int clientSocket;
        struct sockaddr_in clientAddress;
        unsigned int clientAddressLen = sizeof(clientAddress);
        
        while (!fExit)
        {
            std::cout << "server waiting.\n";
            
            clientSocket = accept(mSocket, (struct sockaddr *)&clientAddress, &clientAddressLen);
            if (-1 == clientSocket)
                return;
            
            ServerInfo* info = new ServerInfo(clientSocket, this);
            pthread_t* t = (pthread_t*)malloc(sizeof(pthread_t));
            pthread_create(t, NULL, ServeClient, (void*)info);
        }
    }
    
    void KBServer::Exit()
    { fExit = true; close(mSocket); }
    
    void KBServer::Entities(int clientSocket)
    {
        KnowledgeBase::const_iterator it, end;
        std::vector<Entity> entities;
        
        for (it = mpKnowledgeBase->begin(), end = mpKnowledgeBase->end();
             it != end; ++it)
        {
            entities.push_back(*it->second);
        }
        
        Send(clientSocket, entities);
    }
    
    void KBServer::AddFile(const std::string& path)
    { mpKnowledgeBase->AddFile(path); }
    
    void KBServer::EntitiesWithProperty(int clientSocket)
    {
        Property p = ReadProperty(clientSocket);
        KnowledgeBase::const_iterator it, end;
        std::vector<Entity> entities;
        
        for (it = mpKnowledgeBase->begin(), end = mpKnowledgeBase->end();
             it != end; ++it)
        {
            if (it->second->HasProperty(p))
                entities.push_back(*it->second);
        }
        
        Send(clientSocket, entities);
    }
    
    void KBServer::EntitiesOfType(int clientSocket)
    {
        Property type = ReadProperty(clientSocket);
        KnowledgeBase::const_iterator it, end;
        std::vector<Entity> entities;
        
        for (it = mpKnowledgeBase->begin(), end = mpKnowledgeBase->end();
             it != end; ++it)
        {
            if (it->second->IsType(type))
                entities.push_back(*it->second);
        }
        
        Send(clientSocket, entities);
    }
    
    void KBServer::EntityById(int clientSocket)
    {
        int id = -1;
        read(clientSocket, &id, sizeof(id));
        
        std::stringstream data;
        
        EntityPtrT e = mpKnowledgeBase->EntityPtrById(id);
        if (!e)
            data << "NULL";
        else
            data << e;
        
        SendString(clientSocket, data.str());
    }
    
    void KBServer::EntityByName(int clientSocket)
    {
        std::string name = ReadString(clientSocket);
        
        std::stringstream data;
        
        EntityPtrT e = mpKnowledgeBase->EntityPtrByName(name);
        if (!e)
            data << "NULL";
        else
            data << (*e);
        
        SendString(clientSocket, data.str());
    }
    
    
#pragma mark -
#pragma mark protected
    ///////////////////////////////  PROTECTED   ///////////////////////////////////
    
    void KBServer::SendString(int clientSocket, const std::string& str) const
    {
        if (-1 == clientSocket)
            return;
        if (write(clientSocket, str.c_str(), str.length()) != str.length())
        {
            throw SocketException(clientSocket);
        }
    }
    
    void KBServer::Send(int clientSocket, const std::vector<Entity>& entities)
    {
        std::stringstream ss;
        
        uint64_t numEntities = entities.size();
        
        write(clientSocket, &numEntities, sizeof(numEntities));
        
        
        int i = 0;
        std::vector<Entity>::const_iterator it, end;
        for (it = entities.begin(), end = entities.end();
             it != end; ++it)
        {
            ss << *it << "\n";
            ++i;
            if (ss.str().length() > 10000)
            {
                
#ifdef DEBUG
                if (i % 10 == 0)
                {
                    std::cout << "Sending " << ss.str().length() << " bytes to the client\n";
                    std::cout << "\t: " << i << " entities.\n"; 
                }
#endif
                
                SendString(clientSocket, ss.str());
                
                ss.str("");
            }
        }
        
        if (!ss.str().empty())
            SendString(clientSocket, ss.str());
    }
    
    Property ReadProperty(int clientSocket)
    {
        if (-1 == clientSocket) 
            return Property();
        
        Property p;
        char buffer[1024];
        std::stringstream ss;
        
        int bytes = read(clientSocket, buffer, BUFFER_SIZE-1);
        while (BUFFER_SIZE-1 == bytes)
        {
            buffer[bytes] = '\0';
            ss << buffer;
            bytes = read(clientSocket, buffer, BUFFER_SIZE-1);
        }
        
        buffer[bytes] = '\0';
        ss << buffer;
        
        try
        {
            ss >> p;
            
        } 
        catch (DeserialisationException e)
        {
            std::cerr << "Exception while reading command argument: \n\t";
            std::cerr << e.what() << std::endl;
        }
        
        return p;
    }
    
    std::string ReadString(int clientSocket)
    {
        if (-1 == clientSocket)
            return "";
        
        char buffer[BUFFER_SIZE];
        std::string data = "";
        
        int bytes = read(clientSocket, buffer, BUFFER_SIZE-1);
        while (BUFFER_SIZE-1 == bytes)
        {
            buffer[bytes] = '\0';
            data += buffer;
            bytes = read(clientSocket, buffer, BUFFER_SIZE-1);
        }
        
        buffer[bytes] = '\0';
        data += buffer;
        
        return data;
    }
    
#pragma mark -
#pragma mark protected
    /////////////////////////////// NON-MEMBER ///////////////////////////////////
    
    void* ServeClient(void* data)
    {
#ifdef DEBUG
        std::cout << "Starting client thread.\n";
#endif
        
        ServerInfo* info = (ServerInfo*) data;
        int clientSocket = info->socket_;
        KBServer* serverPtr = info->serverPtr_;
        delete info;
        
        ServerCommand command;
        
        // read request from the client
        read(clientSocket, &command, sizeof(ServerCommand));
        
        try
        {
            while (EXIT != command)
            {
                if (ENTITIES == command) 
                    serverPtr->Entities(clientSocket);
                
                if (ENTITIES_WITH_PROPERTY == command)
                    serverPtr->EntitiesWithProperty(clientSocket);
                
                if (ENTITIES_OF_TYPE == command)
                    serverPtr->EntitiesOfType(clientSocket);
                
                if (ENTITY_BY_NAME == command)
                    serverPtr->EntityByName(clientSocket);
                
                if (ENTITY_BY_ID == command)
                    serverPtr->EntityById(clientSocket);
                
                
                read(clientSocket, &command, sizeof(ServerCommand));
            }
        }
        catch (SocketException e)
        {
            std::cerr << "Error while writing to the socket ";
            std::cerr << e.socket_ << ".\n";
            perror("\t");
        }
#ifdef DEBUG
        std::cout << "Exiting thread.\n";
#endif
        return 0;
    }
    
} // end namespace GRE
}
}
